/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file 
 * \brief Buffer implementation for streaming applications - implementation
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "owndebug.h"
#include "ownutils.h"
#include "buffer.h"
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <limits.h>

//*****************************************************************************
//*****************************************************************************
static void alignToBegin(TBuffer *buffer);
static void recalculateBuffer(TBuffer *buffer, int newsize);
//*****************************************************************************
//*****************************************************************************

TBuffer *bufferCreate(TBuffer *buffer)
{
  //dPrint(4,"Created new TBuffer");
  TBuffer *newBuffer = (TBuffer *)malloc(sizeof(TBuffer));
  assert (newBuffer != NULL);

  bufferInit(newBuffer);

  
  return newBuffer;
}
//*****************************************************************************

TBuffer *bufferCreateEx(TBuffer *buffer,
                        int initial_size, int minimum_increment,
                        int max_buffer, int max_prebuffer)
     
{
  TBuffer *newBuffer = (TBuffer *)malloc(sizeof(TBuffer));
  assert (newBuffer != NULL);

  //dPrint(4,"Created new TBuffer %p", newBuffer);
  bufferInitEx(newBuffer, initial_size, minimum_increment, 
               max_buffer, max_prebuffer);


  return newBuffer;
}
//*****************************************************************************

void bufferInit(TBuffer *buffer)
{
  buffer->maximum_buffer_length = INT_MAX;
  buffer->minimum_buffer_length = 0;
  buffer->maximum_unused_prebuffer = DEFAULT_PREBUFFER_MAX;
  buffer->minimum_buffer_length_increment = getpagesize(); 
  buffer->bytebuffer = (unsigned char*)malloc(buffer->minimum_buffer_length_increment);
  memset(buffer->bytebuffer, 0x00, buffer->minimum_buffer_length_increment);

  buffer->buffer_length = buffer->minimum_buffer_length_increment;
  buffer->data_begin = buffer->bytebuffer;
  buffer->data_begin_index = 0;
  buffer->data_length = 0;
  //dPrint(4,"Initialized new TBuffer %p", buffer);

}
//*****************************************************************************

void bufferInitEx(TBuffer *buffer,
                  int initial_size, int minimum_increment,
                  int max_buffer, int max_prebuffer)
{
  buffer->maximum_buffer_length = max_buffer;
  buffer->minimum_buffer_length = initial_size;
  buffer->maximum_unused_prebuffer = max_prebuffer;
  buffer->minimum_buffer_length_increment = minimum_increment; 
  buffer->bytebuffer = (unsigned char*)malloc(initial_size);
  memset(buffer->bytebuffer, 0x00, initial_size);
  
  buffer->buffer_length = initial_size;
  buffer->data_begin = buffer->bytebuffer;
  buffer->data_begin_index = 0;
  buffer->data_length = 0;
  //dPrint(4,"Initialized new TBuffer %p", buffer);

}
//*****************************************************************************

void bufferDeinit(TBuffer *buffer)
{
  // If pointer is valid, free it
  if (buffer->bytebuffer) free(buffer->bytebuffer);

  //dPrint(4,"Deinitialized TBuffer %p", buffer);
}
//*****************************************************************************

void bufferDestroy(TBuffer *buffer)
{
  // First, deinit the buffer itself
  bufferDeinit(buffer);

  // Last, destroy the data area pointed by 'buffer'
  free(buffer);
    
  //dPrint(4,"Destroyed TBuffer %p", buffer);
} 
//*****************************************************************************

inline int bufferGetLength(const TBuffer *buffer)
{
  return buffer->data_length;
}
//*****************************************************************************

void bufferClear(TBuffer *buffer)
{
  buffer->data_begin = buffer->bytebuffer;
  buffer->data_begin_index = 0;
  buffer->data_length = 0;
}
//*****************************************************************************

void bufferShift(TBuffer *buffer, int shiftBytes)
{
  // Do harsh check on the requested shift size
  assert(shiftBytes <= buffer->data_length);

  // Now, check whether this is a full buffer shift.
  if (shiftBytes == buffer->data_length) {
    // Optimizing; IF the data buffer size equals to total size, 
    // do fast/full CLEAR
    //dPrint(12,"Requested shift is same size as full buffer. Doing Clear");
    bufferClear(buffer);
    
  } else {
    // Calculate new data_begin by adding the shiftBytes to original begin
    buffer->data_begin = buffer->data_begin + shiftBytes;
    buffer->data_begin_index += shiftBytes;
    buffer->data_length -= shiftBytes;

    //dPrint(12,"Shift %d bytes; data_begin_index now: %d, data_length: %d)",
    //      shiftBytes, buffer->data_begin_index, buffer->data_length);
    
    // Now, check whether the new location is inside prebuffer frame limit
    if (buffer->data_begin_index > 
        buffer->maximum_unused_prebuffer) {
      //dPrint(6, "Requested Shift goes outside requested prebuffer! (%d > %d)",
      //       buffer->data_begin_index, buffer->maximum_unused_prebuffer);
      // This operation would yield the prebuffer count to be exceeded.
      // We must do Align now.
      alignToBegin(buffer);
    } else {
      //dPrint(6,"Data is still inside defined maximum Prebuffer (%d < %d)",
      //       buffer->data_begin_index, buffer->maximum_unused_prebuffer);
    }
  }
}
//*****************************************************************************

void bufferAppendSizeHint(TBuffer *buffer, int bytes)
{
  recalculateBuffer(buffer, buffer->data_length+bytes);
}
//*****************************************************************************

void bufferAppend(TBuffer *buffer, const unsigned char *data, int bytes)
{
  // First, call recalculate for the buffer; it does buffer magic.
  recalculateBuffer(buffer, buffer->data_length+bytes);

  // Now, if the recalculate hasn't failed, we have enough space available!
  memcpy(buffer->data_begin+buffer->data_length,
         data, bytes);

  // Reassign values
  buffer->data_length += bytes;
}
//*****************************************************************************

void bufferAppendFromBuffer(TBuffer *buffer,
                            const TBuffer *inbuffer, 
                            int from, int bytes)
{
  // Verify that starting point + requested size is smaller or eql to totalsize
  assert( from+bytes <= bufferGetLength(inbuffer) );

  // Call the ordinary append by using the internal pointers
  bufferAppend(buffer, bufferGetPtr(inbuffer)+from, bytes); 
}
//*****************************************************************************

inline const unsigned char *bufferGetPtr(const TBuffer *buffer)
{
  //dPrint(12,"Data begin at index %d, buffer size %d, got %d bytes in buffer",
  //       buffer->data_begin_index, buffer->buffer_length, buffer->data_length);
  return buffer->data_begin;
}
//*****************************************************************************

void bufferSetMinimumIncrement(TBuffer *buffer, int increment)
{
  buffer->minimum_buffer_length_increment = increment;
}
//*****************************************************************************

void bufferSetMaxBufferSize(TBuffer *buffer, int size)
{
  buffer->maximum_buffer_length = size;
}
//*****************************************************************************

void bufferSetMaxPrebufferSize(TBuffer *buffer, int size)
{
  buffer->maximum_unused_prebuffer = size;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void recalculateBuffer(TBuffer *buffer, int newsize)
{
  // Calculate the required BUFFER size, by taking into account the begin point
  int requiredBufferSize = buffer->data_begin_index+newsize;
  /*dPrint(12,"Required buffer size for %p is %d (%d + %d)",
         buffer,
         buffer->data_begin_index+newsize,
         buffer->data_begin_index, newsize);
  */
  // Recalculate the buffer for the new size 'newsize'
  if (buffer->buffer_length < requiredBufferSize) {
    unsigned char *newBuffer;
    int requiredIncrements = (requiredBufferSize /
                              buffer->minimum_buffer_length_increment)+1;
    buffer->buffer_length = MAX(requiredBufferSize, 
                                (requiredIncrements * 
                                 buffer->minimum_buffer_length_increment));
    
    dPrint(ODVINFO, "Buffer too small for %p. Resizing to %d bytes! (need atleast %d bytes)",
           buffer,
           buffer->buffer_length,
           newsize);
    
    
    newBuffer= (unsigned char *)realloc(buffer->bytebuffer,
                                        buffer->buffer_length);
    assert(newBuffer != NULL);

    if (newBuffer != buffer->bytebuffer) {
      // REQUIRED; if the realloc changes the pointer, everything falls apart!
      buffer->bytebuffer = newBuffer;
      buffer->data_begin = buffer->bytebuffer + buffer->data_begin_index;
      if (buffer->bytebuffer != newBuffer) {
        dPrint(ODVINFO,"NOTE: The Realloc changed the base pointer for %p from %p to %p!",
               buffer, buffer->bytebuffer, newBuffer);
      }
    }
  } else {

    // Do nada at the moment
    //dPrint(12,"Current buffersize for %p, %d is enough for %d bytes data",
    //       buffer, buffer->buffer_length, newsize);
  }
}
//*****************************************************************************

static void alignToBegin(TBuffer *buffer)
{
  //dPrint(12,"Aligning the buffer to beginning of the buffer for %p.", buffer);
  // Move the data to beginning of the buffer
  memmove(buffer->bytebuffer, 
          buffer->data_begin,
          buffer->data_length);

  // Update the pointers
  buffer->data_begin = buffer->bytebuffer;
  buffer->data_begin_index = 0;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
